/** @file res_media_streamer.cpp
	@brief Media resource streamer */

#include "res/res_media_streamer.h"

//==============================================================================
// Sorting functor
//==============================================================================
bool G3::MEDIA_SORT_FUNCTOR::operator() (boost::shared_ptr<MEDIA_OBJECT> a, boost::shared_ptr<MEDIA_OBJECT> b) const {
    if (a != NULL && b != NULL)
        return (a->priority < b->priority);
    return false;
}

//==============================================================================
// Constructors
//==============================================================================
G3::MEDIA_OBJECT::MEDIA_OBJECT () : LOGGABLE () {
    filepos = 0;
    priority = MAX_PRIORITY / 2;
    chunk_size = DEF_CHUNK_SIZE;
    stream = NULL;

    loaded = false;
}

G3::MEDIA_OBJECT::MEDIA_OBJECT (const G3::MEDIA_OBJECT &obj) {
    filepath = obj.filepath;
    filepos = obj.filepos;
    priority = obj.priority;
    chunk_size = obj.chunk_size;
    stream = obj.stream;
    log = obj.log;
    conn_loaded = obj.conn_loaded;
    conn_failed = obj.conn_failed;
    timer = obj.timer;

    loaded = obj.loaded;
}

G3::MEDIA_STREAMER::MEDIA_STREAMER () {
}

//==============================================================================
// Destructors
//==============================================================================
G3::MEDIA_OBJECT::~MEDIA_OBJECT () {
    conn_loaded.clear ();
}

G3::MEDIA_STREAMER::~MEDIA_STREAMER () {
    clear ();
}

G3::MEDIA_OBJECT &G3::MEDIA_OBJECT::operator= (const G3::MEDIA_OBJECT &obj) {
    filepath = obj.filepath;
    filepos = obj.filepos;
    priority = obj.priority;
    chunk_size = obj.chunk_size;
    stream = obj.stream;
    log = obj.log;
    conn_loaded = obj.conn_loaded;
    conn_failed = obj.conn_failed;
    timer = obj.timer;

    return *this;
}

//==============================================================================
// Sets up a callback for a loaded event
//==============================================================================
G3::MEDIA_OBJECT::SIG_CONNECTION G3::MEDIA_OBJECT::doOnLoaded (G3::MEDIA_OBJECT::LOADED_HANDLER aLoadedHandler) {
	Assert (aLoadedHandler, "MEDIA_OBJECT::doOnLoaded: aLoadedHandler argument is a NULL pointer");

	SIG_CONNECTION c = sig_loaded.connect (aLoadedHandler);
	conn_loaded.push_back (c);

	return c;
}

//==============================================================================
// Sets up a callback for a failed event
//==============================================================================
G3::MEDIA_OBJECT::SIG_CONNECTION G3::MEDIA_OBJECT::doOnFailed (G3::MEDIA_OBJECT::FAILED_HANDLER aFailedHandler) {
	Assert (aFailedHandler, "MEDIA_OBJECT::doOnFailed: aFailedHandler argument is a NULL pointer");

	SIG_CONNECTION c = sig_failed.connect (aFailedHandler);
	conn_failed.push_back (c);

	return c;
}

//==============================================================================
// Streamer thread functor
//==============================================================================
void G3::MEDIA_STREAMER::process () {
    if (log)
        log->Report ("Media streamer thread online..\n");

    online = true;

    while (online) {
        // Add and remove pending objects
        update_add ();
        update_remove ();
        // Update
        update ();

        // Idle for some time
        boost::thread::yield ();
    }
}

//==============================================================================
// Adds a new media object into the list
//==============================================================================
bool G3::MEDIA_STREAMER::add (const boost::shared_ptr<MEDIA_OBJECT> &obj) {
    if (obj == NULL) {
        if (log)
            log->Report ("MEDIA_STREAMER::add: Trying to add a NULL pointer\n");
        return false;
    }

    // Add the object into the pending batches list
    mutex_add.lock ();
    pending_add.insert (obj);
    mutex_add.unlock ();

    return true;
}

//==============================================================================
// Removes a media object from the list
//==============================================================================
bool G3::MEDIA_STREAMER::remove (const boost::shared_ptr<MEDIA_OBJECT> &obj) {
    if (obj == NULL) {
        if (log)
            log->Report ("MEDIA_STREAMER::remove: Trying to remove a NULL pointer\n");
        return false;
    }

    // Add the object into the pending remove objects list
    mutex_remove.lock ();
    pending_remove.insert (obj);
    mutex_remove.unlock ();

    return true;
}

//==============================================================================
// Dumps the batch sorter
//==============================================================================
void G3::MEDIA_STREAMER::dump () {
    std::set<boost::shared_ptr<MEDIA_OBJECT>, MEDIA_SORT_FUNCTOR>::iterator it, it2;
    std::string batch_key;
    int id = 0;

    if (log) {
        log->Report ("Dumping the media streamer:\n");
        log->Report (" Online: %d\n", online);
        log->Report (" Media Objects (%d):\n", objects.size ());

        it = objects.begin ();
        while (it != objects.end ()) {
            log->Report ("  Object %d: 0x%X\n", id, (*it).get ());

            //! \todo Dump more information

            it++;
            id++;
        }

        id = 0;

        log->Report (" Pending (Add %d):\n", pending_add.size ());

        it2 = pending_add.begin ();
        while (it2 != pending_add.end ()) {
            log->Report ("  Object %d: 0x%X\n", id, (*it2).get ());

            //! \todo Dump more information

            it2++;
            id++;
        }

        id = 0;

        log->Report (" Pending (Remove %d):\n", pending_remove.size ());

        it2 = pending_remove.begin ();
        while (it2 != pending_remove.end ()) {
            log->Report ("  Object %d: 0x%X\n", id, (*it2).get ());

            //! \todo Dump more information

            it2++;
            id++;
        }
    }
}

//==============================================================================
// Clears the list of batches
//==============================================================================
void G3::MEDIA_STREAMER::clear () {
    if (!objects.empty ())
        objects.clear ();
    if (!pending_add.empty ())
        pending_add.clear ();
    if (!pending_remove.empty ())
        pending_remove.clear ();
}

//==============================================================================
// Updates the list of objects by adding the pending objects
//! \bug Probably here
//==============================================================================
void G3::MEDIA_STREAMER::update_add () {
    if (pending_add.empty ())
        return;

    std::set<boost::shared_ptr<MEDIA_OBJECT>, MEDIA_SORT_FUNCTOR>::iterator it, it2;
    MEDIA_SORT_FUNCTOR sorter;

    mutex_add.lock ();

    it = pending_add.begin ();
    while (it != pending_add.end ()) {
        if (*it == NULL) {
            it++;
            continue;
        }

        // Make sure that it doesn't already exist
        if (objects.empty () || !std::binary_search (objects.begin (), objects.end (), *it, sorter)) {
            mutex_objects.lock ();
            objects.insert (*it);
            mutex_objects.unlock ();
        }

        it++;
    }

    pending_add.clear ();

    mutex_add.unlock ();
}

//==============================================================================
// Updates the list of objects by removing the pending objects
//==============================================================================
void G3::MEDIA_STREAMER::update_remove () {
    if (objects.empty ())
        return;

    std::set<boost::shared_ptr<MEDIA_OBJECT>, MEDIA_SORT_FUNCTOR>::iterator it, it2;

    // Remove pending objects - takes a lot of time
    if (!pending_remove.empty ()) {
        mutex_remove.lock ();

        // Is there anything to remove at all ?
        it = pending_remove.begin ();

        while (it != pending_remove.end ()) {
            // And find the first entry that is equal to the pending remove object
            it2 = std::find (objects.begin (), objects.end (), *it);

            if (it2 != objects.end () && *it2 == *it) {
                mutex_objects.lock ();
                objects.erase (it2);
                mutex_objects.unlock ();
            }

            it++;
        }

        pending_remove.clear ();

        mutex_remove.unlock ();
    }
}

void G3::MEDIA_STREAMER::update () {
    std::set<boost::shared_ptr<MEDIA_OBJECT>, MEDIA_SORT_FUNCTOR>::iterator it;
    long bytes = 0;
    G3::MEDIA_OBJECT::READ_RESULT result;

    mutex_objects.lock ();

    it = objects.begin ();
    while (it != objects.end ()) {
        if ((*it != NULL) && (*it)->stream) {
            // Start?
            if ((*it)->filepos == 0)
                (*it)->timer.Start ();

			// Valgrind: Invalid read of size 4
			// Valgrind: Invalid read of size 1
            bytes = (*it)->stream->BufferFromFile ((*it)->filepath, (*it)->chunk_size, (*it)->filepos);

            // Finished loading ?
            if (bytes == 0) {
                (*it)->loaded = true;

                remove (*it);

                (*it)->timer.CheckTime ();
				// Valgrind: Invalid read of size 4
                (*it)->sig_loaded ();

				break;
            }

            (*it)->filepos += bytes;

            // Read the streamed chunk
            result = (*it)->read ();

            // Detach on error
            if (result == G3::MEDIA_OBJECT::RR_ERROR) {
                (*it)->timer.CheckTime ();

                (*it)->sig_failed ((*it)->filepos);

                remove (*it);

                break;

            // Signal & detach on done
            } else if (result == G3::MEDIA_OBJECT::RR_DONE) {
                (*it)->loaded = true;

                remove (*it);

                (*it)->timer.CheckTime ();
				// Valgrind: Invalid read of size 4
                (*it)->sig_loaded ();

				break;
            }
		}

        // Idle for some time
        boost::thread::yield ();

        it++;
    }

    mutex_objects.unlock ();
}
